﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Windows.Forms.Design;

namespace Zsg_PeerToPeer
{
    public class StateObject
    {
        public Socket workSocket = null;
        public const int BufferSize = 100;
        public byte[] buffer = new byte[BufferSize];
        public List<byte> buffers = new List<byte>();
        //是不是和服务器的链接
        public bool IsServerCon = false;
    }
    /// <summary>
    /// 打洞节点客户端 实现的功能：
    /// 连接服务器获取对方节点ip 
    /// 请求对方ip（打洞）
    /// 根据条件判断是监听连接还是监听等待连接
    /// </summary>
    public class PeerClient : IPeerClient
    {
        //ManualResetEvent xxxxDone =  new ManualResetEvent(false);
        //Semaphore 
        /// <summary>
        /// 当前链接
        /// </summary>
        public Socket Client { get;private set; }

        #region 服务端
        public string ServerHostName { get;private set; }
        public int ServerPort { get; private set; }
        #endregion

        #region 接收和通知事件
        public delegate void EventMsg(object sender, string e);
        //接收事件
        public event EventMsg ReceivedMsg;
        //通知消息
        public event EventMsg NoticeMsg;
        #endregion

        //本地绑定的节点
        private IPEndPoint LocalEP;

        public PeerClient(string hostname, int port)
        {
            Client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            this.ServerHostName = hostname;
            this.ServerPort = port;

        }
        /// <summary>
        /// 初始化客户端（包括启动）
        /// </summary>
        public void Init()
        {
            try
            {
                Client.Connect(ServerHostName, ServerPort);
            }
            catch (SocketException ex)
            {
                NoticeMsg?.Invoke(Client, $"连接服务器失败！{ex}！\r\n");
                throw;
            }
            catch (Exception ex)
            {
                NoticeMsg?.Invoke(Client, $"连接服务器失败！{ex}！\r\n");
                throw;
            }
            NoticeMsg?.Invoke(Client, $"连接上服务器了！\r\n");
            var _localEndPoint = Client.LocalEndPoint.ToString();
            LocalEP = new IPEndPoint(IPAddress.Parse(_localEndPoint.Split(':')[0])
                , int.Parse(_localEndPoint.Split(':')[1]));
            Receive(Client);

        }
        private void Receive(Socket client)
        {
            try
            {
                StateObject state = new StateObject();
                state.workSocket = client;
                state.IsServerCon = true;
                client.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0,
                    new AsyncCallback(ReceiveCallback), state);
            }
            catch (Exception e)
            {
                NoticeMsg?.Invoke(Client, $"接收消息出错了{e}！\r\n");
            }
        }
        private void ReceiveCallback(IAsyncResult ar)
        {
            try
            {
                var state = (StateObject)ar.AsyncState;
                Socket _client = state.workSocket;
                //因为到这边的经常Connected 还是true
                //if (!_client.Connected)
                //{
                //    _client.Close();
                //    return;
                //}
                SocketError error = SocketError.Success;
                int bytesRead = _client.EndReceive(ar,out error);
                if (error == SocketError.ConnectionReset)
                {
                    NoticeMsg?.Invoke(Client, $"链接已经释放！\r\n");
                    _client.Close();
                    _client.Dispose();
                    return;
                }
                if (SocketError.Success!= error)
                {
                    throw new SocketException((int)error);
                }
                var arr = state.buffer.AsQueryable().Take(bytesRead).ToArray();
                state.buffers.AddRange(arr);

                if (bytesRead >= state.buffer.Length)
                {
                    _client.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0,
                      new AsyncCallback(ReceiveCallback), state);
                    ////state.buffers.CopyTo(state.buffers.Count, state.buffer, 0, bytesRead);
                    //_client.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0,
                    //    new AsyncCallback(ReceiveCallback), state);
                }
                else
                {
                    var _msg = Encoding.UTF8.GetString(state.buffers.ToArray());
                    ReceivedMsg?.Invoke(_client, _msg);
                    if (state.IsServerCon)
                    {
                        _client.Shutdown(SocketShutdown.Both);
                        _client.Close();
                        int retryCon = _msg.Contains("_1") ? 1 : 100;
                        _msg = _msg.Replace("_1", "").Replace("_2", "");
                        TryConnection(_msg.Split(':')[0], int.Parse(_msg.Split(':')[1]), retryCon);
                        return;
                    }
                    state = new StateObject();
                    state.IsServerCon = false;
                    state.workSocket = _client;
                    _client.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0,
                     new AsyncCallback(ReceiveCallback), state);

                }
            }             
            catch (SocketException ex)
            {
                //10054
                NoticeMsg?.Invoke(Client, $"链接已经释放！{ex}！\r\n");

            }
            catch (Exception e)
            {
                NoticeMsg?.Invoke(Client, $"接收消息出错了2{e}！\r\n");
            }
        }
        /// <summary>
        /// 打洞或者尝试链接
        /// </summary>
        private void TryConnection(string remoteHostname, int remotePort,int retryCon)
        {
            Client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
            var _iPRemotePoint = new IPEndPoint(IPAddress.Parse(remoteHostname), remotePort);
            Client.Bind(LocalEP);
            System.Threading.Thread.Sleep(retryCon==1?1:3*1000);
            for (int i = 0; i < retryCon; i++)
            {
                try
                {
                    Client.Connect(_iPRemotePoint);
                    NoticeMsg?.Invoke(Client, $"已经连接上：{remoteHostname}:{remotePort}！\r\n");
                    StateObject state = new StateObject();
                    state.workSocket = Client;
                    state.IsServerCon = false;
                    Client.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0,
                     new AsyncCallback(ReceiveCallback), state);
                    return;
                }
                catch
                {
                    NoticeMsg?.Invoke(Client, $"尝试第{i+1}次链接：{remoteHostname}:{remotePort}！\r\n");
                }
            }
            if (retryCon==1)
            {
                Listening(LocalEP.Port);
                return;
            }
            NoticeMsg?.Invoke(Client, $"尝试了{retryCon}次都没有办法连接到：{remoteHostname}:{remotePort}，凉了！\r\n");

        }
        /// <summary>
        /// 如果连接不成功，因为事先有打洞过了，根据条件监听 等待对方连接来
        /// </summary>
        private void Listening(int Port)
        {
            try
            {
                Client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
                Client.Bind(new IPEndPoint(IPAddress.Any, Port));
                Client.Listen((int)SocketOptionName.MaxConnections);
                NoticeMsg?.Invoke(Client, $"开始侦听断开等待链接过来！\r\n");
                StateObject state = new StateObject();
                state.IsServerCon = false;
                var _socket = Client.Accept();//只有一个链接 不用BeginAccept
                Client.Close();//关系现有侦听
                Client = _socket;
                state.workSocket = Client;
                NoticeMsg?.Invoke(Client, $"接收到来自{Client.RemoteEndPoint}的连接！\r\n");
                Client.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0,
                    new AsyncCallback(ReceiveCallback), state);
            }
            catch (Exception ex)
            {

                NoticeMsg?.Invoke(Client, $"监听出错了{ex}凉了！\r\n");
            }
            //scoket.send



        }
        /// <summary>
        /// 本例子只存在一个成功的链接，对成功的连接发送消息！
        /// </summary>
        /// <param name="strMsg"></param>
        public void Send(string strMsg)
        { 
            byte[] bytes = Encoding.UTF8.GetBytes(strMsg); 
            Client.BeginSend(bytes, 0, bytes.Length, 0,
                new AsyncCallback(SendCallback), Client);
        }
        private void SendCallback(IAsyncResult ar)
        {
            try
            { 
                Socket _socket = (Socket)ar.AsyncState;
                //if(ar.IsCompleted)
                _socket.EndSend(ar); 
            }
            catch (Exception e)
            {
                NoticeMsg?.Invoke(Client, $"发送消息出错了{e}！\r\n");
            }
        }
    }
}
